//***************************************************************************
//   Copyright(c)2020, Xidian University D405.
//           All rights reserved
//
//   File name       :   axi_boundary_checker.v
//   Module name     :   axi_boundary_checker
//   Author          :   Zhao Yuchen
//   Date            :   2022/05/17
//   Version         :   2.00
//   Changelog       :   v2.00 All outputs use registers to beat.
//   Edited by       :   Zhao Yuchen
//***************************************************************************
module axi_boundary_checker #
(   parameter CROSS_4K_EN     = 1,
    parameter AXI_ID_WIDTH    = 4,
    parameter AXI_ADDR_WIDTH  = 32,
    parameter AXI_DATA_WIDTH  = 256,
    parameter AXI_LIB_WIDTH   = 13
)
(
    input wire                          clk,
    input wire                          rstn,
    input wire  [AXI_ID_WIDTH-1 : 0]    id_i,
    input wire  [AXI_ADDR_WIDTH-1 : 0]  addr_i,
    input wire  [AXI_LIB_WIDTH-1 : 0]   wbytes_i,
    input wire                          desc_en_i,
    input wire                          desc_type_i,
    input wire                          fifo_sel_i,
    output wire                         din_ready,
    input wire                          din_valid,
    output reg  [AXI_ID_WIDTH-1 : 0]    id_o,    
    output reg  [AXI_ADDR_WIDTH-1 : 0]  addr_o,
    output reg  [AXI_LIB_WIDTH-1 : 0]   wbytes_o,
    output reg  [AXI_LIB_WIDTH-1 : 0]   wbytes_total_o,
    output reg                          desc_en_o,
    output reg                          desc_type_o,
    output reg                          fifo_sel_o,
    //output reg                          cross_4k_flag,
    output reg                          begin_of_a_task,
    output reg                          end_of_a_task,
    input wire                          dout_ready,
    output reg                          dout_valid
);

    function integer clogb2 (input integer bit_depth);              
    begin: bit_width_calc
        integer bit_depth_tmp;    
        bit_depth_tmp = bit_depth;                                         
        for(clogb2=0; bit_depth_tmp>0; clogb2=clogb2+1)                   
            bit_depth_tmp = bit_depth_tmp >> 1;                                 
    end                                                           
    endfunction  

    localparam  S_IDLE = 2'b01;
    localparam  S_SEND = 2'b10;
    localparam integer ADDR_ALIGN_BOUNDARY = AXI_DATA_WIDTH / 'd8;
    localparam integer MASK_BIT_NUM = AXI_ADDR_WIDTH - 'd12;
    
    reg  [1:0]                    state;
    reg  [1:0]                    next_state;
    reg  [AXI_ID_WIDTH-1 : 0]     id_r;
    //reg  [AXI_ADDR_WIDTH-1 : 0]   addr_r;
    reg  [AXI_LIB_WIDTH-1 : 0]    wbytes_r;
    reg                           desc_en_r;
    reg                           desc_type_r;
    reg                           fifo_sel_r;
    wire                          burst_cross_4k_flag;
    reg  [AXI_LIB_WIDTH-1 : 0]    wbytes_send;
    reg  [AXI_LIB_WIDTH-1 : 0]    wbytes_remain;
    reg  [AXI_ADDR_WIDTH-1 : 0]   addr_ptr;
    wire                          send_next;
    reg  [AXI_ADDR_WIDTH-1 : 0]   addr_start;
    reg  [AXI_ADDR_WIDTH-1 : 0]   addr_end;
    wire                          start;
    reg                           start_r;
    reg  [AXI_ADDR_WIDTH-1 : 0]   addr_start_ff;
    reg  [AXI_ADDR_WIDTH-1 : 0]   addr_end_ff;
    wire [MASK_BIT_NUM-1 : 0]     addr_mask_a;
    wire [11 : 0]                 addr_mask_b;
    wire [AXI_ADDR_WIDTH-1 : 0]   addr_mask;
    wire  [AXI_ADDR_WIDTH-1 : 0]  addr_end_current;
    wire  [AXI_ADDR_WIDTH-1 : 0]  addr_ptr_next;
    wire [AXI_ADDR_WIDTH-1 : 0]   addr_align;
    reg                           din_ready_0;
    wire                          end_of_a_task_0;
    
    assign addr_align = {addr_i[39:4],4'b0000};// - (addr_i % ADDR_ALIGN_BOUNDARY);
    assign burst_cross_4k_flag = CROSS_4K_EN ? (|(addr_end[AXI_ADDR_WIDTH-1:12] - addr_start[AXI_ADDR_WIDTH-1:12])) : 1'b0;
    //assign addr_start          = addr_r;
    //assign addr_end            = addr_start + wbytes_r - 'd1;
    assign din_ready           = din_ready_0 & dout_ready;//= (state == S_IDLE) && dout_ready;
    //assign begin_of_a_task     = (din_valid && ~burst_cross_4k_flag) || (start_r && burst_cross_4k_flag);
    assign end_of_a_task_0     = (~burst_cross_4k_flag || (wbytes_remain == wbytes_send));
    assign send_next           = dout_ready;
    assign start               = din_valid && burst_cross_4k_flag;
    assign addr_mask_a  = {MASK_BIT_NUM{1'b1}};
    assign addr_mask_b  = 12'b0;
    assign addr_mask    = {addr_mask_a, addr_mask_b};
    assign addr_end_current = (addr_ptr|(~addr_mask));
    assign addr_ptr_next = addr_ptr + wbytes_send + 'd1;
    
    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
        begin
            addr_start_ff <= 'd0;
            addr_end_ff   <= 'd0;
        end
        else if(din_valid)
        begin
            addr_start_ff <= addr_start;
            addr_end_ff   <= addr_end;
        end
        else
        begin
            addr_start_ff <= addr_start_ff;
            addr_end_ff   <= addr_end_ff;
        end
    end
    
    always @(*)
    begin
        if(din_valid)
        begin
            addr_start = addr_align;
            addr_end   = addr_start + wbytes_i;
        end
        else if(state == S_SEND)
        begin
            addr_start = addr_start_ff;//addr_r;
            addr_end   = addr_end_ff;//addr_start + wbytes_r - 'd1;
        end
        else
        begin
            addr_start = 'd0;
            addr_end   = 'd0;
        end
    end
    
    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
            state <= S_IDLE;
        else
            state <= next_state;
    end
    
    always @(*)
    begin
        case(state)
            S_IDLE:
            begin
                if(start)
                    next_state = S_SEND;
                else
                    next_state = S_IDLE;
            end
            S_SEND:
            begin
                if(end_of_a_task_0 == 1'b1)
                    next_state = S_IDLE;
                else
                    next_state = S_SEND;
            end
            default:
            begin
                next_state = S_IDLE;
            end
        endcase
    end

    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
            start_r <= 1'b0;
        else
            start_r <= start;
    end
    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
        begin
            //addr_r      <= 'b0;
            wbytes_r    <= 'b1;
            id_r        <= 'b0;
            desc_en_r   <= 'b0;
            desc_type_r <= 'b0;
            fifo_sel_r  <= 'b0;
        end
        else if(start)
        begin
            //addr_r      <= addr_align;
            wbytes_r    <= wbytes_i + 'd1;
            id_r        <= id_i;
            desc_en_r   <= desc_en_i;
            desc_type_r <= desc_type_i;
            fifo_sel_r  <= fifo_sel_i;
        end
        else if(end_of_a_task_0 == 1'b1 || (state != S_SEND))
        begin
            //addr_r      <= 'b0;
            wbytes_r    <= 'b1;
            id_r        <= 'b0;
            desc_en_r   <= 'b0;
            desc_type_r <= 'b0;
            fifo_sel_r  <= 'b0;
        end
        else
        begin
            //addr_r      <= addr_r;
            wbytes_r    <= wbytes_r;
            id_r        <= id_r;
            desc_en_r   <= desc_en_r;
            desc_type_r <= desc_type_r;
            fifo_sel_r  <= fifo_sel_r;
        end
    end
    
    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
        begin
            addr_ptr <= 'h0;
        end
        else
        begin
            if(start)
                addr_ptr <= addr_align;
            else if(state == S_SEND && ~end_of_a_task_0)
                addr_ptr <= addr_ptr_next;
            else if(end_of_a_task_0 == 1'b1)
                addr_ptr <= 'h0;
            else
                addr_ptr <= addr_ptr;   
        end
    end
    
    
    always @(posedge clk or negedge rstn)
    begin
        if(rstn == 1'b0)
        begin
            wbytes_remain <= 'd0;
        end
        else
        begin
            if(start)
                wbytes_remain <= wbytes_i - wbytes_send;
            else if(end_of_a_task_0 == 1'b1)
                wbytes_remain <= 'd0;
            else if(state == S_SEND)
                wbytes_remain <= (wbytes_remain - 'd1) - wbytes_send;
            else
                wbytes_remain <= wbytes_remain;
        end
    end
    
    
    always @(posedge clk or negedge rstn)
    begin
        if(~rstn)
            wbytes_send <= 'd0;
        else if(start)
            wbytes_send <= (addr_align|(~addr_mask)) - addr_align;
        //else if(wbytes_remain != 'd0)
        else if(end_of_a_task_0 == 1'b1)
            wbytes_send <= 'd0;
        else if(state == S_SEND)
        begin
            if(addr_end_ff != addr_end_current)// no more cross 4K
                wbytes_send   <= addr_end_ff - addr_ptr_next;
            else//(addr_end > addr_ptr | 32'h0000_0FFF)
                wbytes_send   <= addr_end_current - addr_ptr_next;
        end
        else
            wbytes_send <= 'd0;
        //end
        //else
        //begin
        //    wbytes_send   = 'd1;
            //cross_4k_flag = 'b0; 
        //end
    end
    
    //assign begin_of_a_task     = (din_valid && ~burst_cross_4k_flag) || (start_r && burst_cross_4k_flag);
    //assign end_of_a_task       = (~burst_cross_4k_flag || (wbytes_remain == wbytes_send));
    
    always @(posedge clk or negedge rstn)
    begin
        if(~rstn)
        begin
            din_ready_0     <= 'd0;
            dout_valid      <= 'd0;
            id_o            <= 'd0;
            addr_o          <= 'd0;
            wbytes_o        <= 'd0;
            desc_en_o       <= 'd0;
            desc_type_o     <= 'd0;
            fifo_sel_o      <= 'd0;
            wbytes_total_o  <= 'd0;
            begin_of_a_task <= 'd0;
            end_of_a_task   <= 'd0;
        end
        else
        begin
            din_ready_0      <= (state == S_IDLE) && ~dout_valid;
            dout_valid       <= (din_valid && ~burst_cross_4k_flag) || (state == S_SEND);
            id_o             <= din_valid ? id_i : id_r;
            addr_o           <= din_valid ? addr_align : addr_ptr;
            wbytes_o         <= burst_cross_4k_flag ? (wbytes_send) : (din_valid ? wbytes_i : 'd0);//(din_valid | burst_cross_4k_flag) ? ((~burst_cross_4k_flag) ? wbytes_i : wbytes_send);
            desc_en_o        <= din_valid ? desc_en_i : desc_en_r;
            desc_type_o      <= din_valid ? desc_type_i : desc_type_r;
            fifo_sel_o       <= din_valid ? fifo_sel_i : fifo_sel_r;
            wbytes_total_o   <= din_valid ? wbytes_i : (wbytes_r-'d1);
            begin_of_a_task  <= (din_valid && ~burst_cross_4k_flag) || (start_r && burst_cross_4k_flag); 
            end_of_a_task    <= end_of_a_task_0;                
        end
    end
    
    /*
    always @(*)
    begin
        dout_valid     = (din_valid && ~burst_cross_4k_flag) || (state == S_SEND);
        id_o           = din_valid ? id_i : id_r;
        addr_o         = din_valid ? addr_align : addr_ptr;
        wbytes_o       = (~burst_cross_4k_flag) ? wbytes_i : wbytes_send;
        desc_en_o      = din_valid ? desc_en_i : desc_en_r;
        desc_type_o    = din_valid ? desc_type_i : desc_type_r;
        fifo_sel_o     = din_valid ? fifo_sel_i : fifo_sel_r;
        wbytes_total_o = din_valid ? wbytes_i : (wbytes_r-'d1);
    end
    */
    
endmodule
